/*
 * This file is part of Space Invaders: Rehash
 * Copyright (C)2007 Vicent Marti (tanoku@gmail.com)
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <limits.h>
#include <sys/stat.h>

#ifdef _WIN32
	#include "windows.h"
	#include <tchar.h>
#endif

#include "SDL.h"
#include "SDL_ttf.h"
#include "SDL_opengl.h"
#include "SDL_mixer.h"

#include "main.h"
#include "font_handler.h"
#include "timer.h"
#include "gfx.h"
#include "cutscene.h"
#include "game.h"
#include "menu.h"

SDL_Surface	*g_Screen;
Mix_Chunk *g_Sounds[SOUND_MAX];
Mix_Music *g_MainMusic;
bool g_Keys[KEY_MAX];


extern FontHandler *g_Fonts;
extern GLuint g_Textures[TEXTURE_COUNT];
extern int g_CustomGameRules;
extern int g_ContinueGame;
extern int g_Options;

std::string sound_files[SOUND_MAX] =
{
	"sound/shoot.wav",
	"sound/invaderkilled.wav",
	"sound/explosion.wav",
	"sound/fastinvader1.wav",
	"sound/missile_fly.wav",
	"sound/fastinvader2.wav",
	"sound/shoot.wav",
	"sound/music.wav"
};

// TODO: make this crap actually have sense, instead of using random strings
std::string level_passwords[] =
{
	"ILPU",
	"BJJK",
	"RRVF",
	"ZUBT",
	"YDSF",
	"XYMN",
	"RKNF",
	"YFXN",
	"HPFE",
	"CFFO",
	"CAEP",
	"WICK",
	"EDQZ",
	"BRGM",
	"XOCU",
	"QUGU"	
};

// TODO: learning curve maybe is a tad too step? soften up?
// whatever, I suck at this design crap.
int level_rules[] = 
{
	//--------------------------------------------------
	// ACT 1
	//--------------------------------------------------
	// level 1
	GR_HIGH_ENEMY_COUNT|GR_ENEMIES_DONT_ATTACK, 
	
	// level 2
	GR_HIGH_ENEMY_COUNT|GR_STATIC_DEFENCES, 
	
	// level 3
	GR_HIGH_ENEMY_COUNT|GR_PLACE_DEFENCES_4, 
	
	// level 4
	GR_HIGH_ENEMY_COUNT|GR_PLACE_DEFENCES_4|
	GR_MISSILES_BOUNCE_ON_TOP, 
	
	//--------------------------------------------------
	// ACT 2
	//--------------------------------------------------
	// level 1
	GR_MED_ENEMY_COUNT|GR_COLORSHOOTER|GR_4_COLORS, 
	
	// level 2
	GR_MED_ENEMY_COUNT|GR_COLORSHOOTER|GR_4_COLORS|
	GR_MISSILES_BOUNCE_ON_ENEMIES|GR_MISSILES_BOUNCE_ON_TOP, 
	
	// level 3
	GR_HIGH_ENEMY_COUNT|GR_COLORSHOOTER|GR_4_COLORS|
	GR_MISSILES_BOUNCE_ON_ENEMIES|GR_PLACE_DEFENCES_4|
	GR_MISSILES_BOUNCE_ON_TOP, 
	
	// level 4
	GR_HIGH_ENEMY_COUNT|GR_COLORSHOOTER|GR_6_COLORS|
	GR_MISSILES_BOUNCE_ON_ENEMIES|GR_PLACE_DEFENCES_2|
	GR_MISSILES_BOUNCE_ON_TOP, 
	
	//--------------------------------------------------
	// ACT 3
	//--------------------------------------------------
	// level 1
	GR_LOW_ENEMY_COUNT|GR_PRIMESHOOTER|GR_PRIMES_MAX_20|
	GR_MISSILES_BOUNCE_ON_ENEMIES|GR_MISSILES_BOUNCE_ON_TOP, 
	
	// level 2
	GR_LOW_ENEMY_COUNT|GR_PRIMESHOOTER|GR_PRIMES_MAX_100|
	GR_MISSILES_BOUNCE_ON_ENEMIES|GR_MISSILES_BOUNCE_ON_TOP, 
	
	// level 3
	GR_MED_ENEMY_COUNT|GR_PRIMESHOOTER|GR_PRIMES_MAX_100|
	GR_MISSILES_BOUNCE_ON_ENEMIES|GR_MISSILES_BOUNCE_ON_TOP, 
	
	// level 4
	GR_MED_ENEMY_COUNT|GR_PRIMESHOOTER|GR_PRIMES_MAX_500|
	GR_MISSILES_BOUNCE_ON_ENEMIES|GR_MISSILES_BOUNCE_ON_TOP|
	GR_FAST_SHOOTING, 
	
	//--------------------------------------------------	
	// ACT 4
	//--------------------------------------------------
	// level 1
	GR_HIGH_ENEMY_COUNT|GR_STATIC_DEFENCES|GR_MISSILES_BOUNCE_ON_ENEMIES|
	GR_MISSILES_BOUNCE_ON_TOP|GR_TOUGH_ENEMIES, 
	
	// level 2
	GR_MED_ENEMY_COUNT|GR_TOUGH_ENEMIES|GR_PLACE_DEFENCES_4|
	GR_COLORSHOOTER|GR_8_COLORS|GR_MISSILES_BOUNCE_ON_ENEMIES|
	GR_MISSILES_BOUNCE_ON_TOP, 
	
	// level 3
	GR_HIGH_ENEMY_COUNT|GR_PRIMESHOOTER|GR_PRIMES_MAX_1000|
	GR_MISSILES_BOUNCE_ON_ENEMIES|GR_MISSILES_BOUNCE_ON_TOP|
	GR_FAST_SHOOTING|GR_FAST_ENEMIES|GR_ENEMIES_DONT_ATTACK, 
	
	// level 4
	GR_BOSSFIGHT_1 
};

void genEratosthenes();

// HACKETY-HACK: evul hack for Linux, should find the executable path no
// matter where you run the game from... we need a counterpart for win32 too...
// and maybe something for macOS... ok, disregard that, mac users suck cock.
std::string getDataPath()
{
#ifdef USE_PATH_HACK_ON_LINUX
	char buffer[PATH_MAX], linkname[64];

	if (snprintf(linkname, sizeof(linkname), "/proc/%i/exe", (int)getpid()) < 0)
		return "data/";

	if (readlink(linkname, buffer, PATH_MAX) == -1)
		return "data/";

	std::string ret = buffer;
	return ret.substr( 0, ret.find_last_of("/")) + "/data/";
#else
	// TODO: what am I going to do in win32? pray?
	return "data/";
#endif	
}

void loadSounds()
{
	std::string filename;
	for ( int i=0; i<SOUND_MAX; ++i )
	{
		filename = getDataPath() + sound_files[i];
		g_Sounds[i] = Mix_LoadWAV( filename.c_str() );
		if ( g_Sounds[i] == NULL )
		{
			printf("Failed to load sound: %s.\n", filename.c_str() );
		}
	}

	std::string musicpath = getDataPath() + "sound/music.ogg";
	g_MainMusic = Mix_LoadMUS(musicpath.c_str());
}

void Intro::render()
{
	glEnable2D();

	glDrawTexuredRect( SCREEN_W/2 - 256, SCREEN_H/2-275, 512, 512, g_Textures[16] );

	glEnable(GL_BLEND);
	if ( m_animation == ANIMATION_FADEOUT )
	{
		float progress = (float)m_animationTimer.get_ticks()/(float)ANIMATION_FADE_TIME;
		if ( progress > 0.7 )
		{
			m_finished = true;
		}
		else
		{
			glDrawRect( 0, 0, SCREEN_W, SCREEN_H, 0, 0, 0, progress );
		}
	}
	
	glDisable(GL_BLEND);
	glDisable2D();
}

GameManager::GameManager()
{
	m_activeScreen = NULL;
	m_seenRules = 0;
	
	m_nextScreen = SCREEN_LOAD;
		
	newScreen(m_nextScreen);
}

void GameManager::newScreen( int nextScreen )
{
	if ( m_activeScreen )
		return;
	
	m_nextScreen = 0;
	SI_Screen *newScreen = NULL;
	
	switch( nextScreen )
	{
		case SCREEN_LOAD:
			newScreen = new Intro();
			break;

		case SCREEN_GAME_TEST:
			newScreen = new Game( GR_BOSSFIGHT_1, -1 );
			break;

		case SCREEN_GAME_CUSTOM:
			m_currentLevel = -1;	
			newScreen = new Game(g_CustomGameRules, -1);
			break;
		
		case SCREEN_MAINMENU:
			newScreen = new MainMenu();
			break;
		
		case SCREEN_CUTSCENE_TEST:
			newScreen = new CutScene(0, GR_LOW_ENEMY_COUNT|GR_4_COLORS|GR_PRIMES_MAX_20|GR_FAST_ENEMIES|GR_COLORSHOOTER);
			break;
			
		case SCREEN_GAME_NEW:
			m_seenRules = GR_HIGH_ENEMY_COUNT|GR_MED_ENEMY_COUNT|GR_LOW_ENEMY_COUNT;
			m_currentLevel = 0;
			newScreen = new CutScene(m_currentLevel, m_seenRules);
			m_seenRules |= level_rules[m_currentLevel];
			break;
			
		case SCREEN_GAME_NEWLEVEL:
			newScreen = new Game( level_rules[m_currentLevel], m_currentLevel );
			break;
			
		case SCREEN_GAME_NEWCUTSCENE:
			if ( m_currentLevel != -1 )
			{
				m_currentLevel++;
				newScreen = new CutScene(m_currentLevel, m_seenRules);
				m_seenRules |= level_rules[m_currentLevel];
			}
			else
			{
				newScreen = new MainMenu();
			}
			break;
			
		case SCREEN_GAME_CONTINUE:
			m_currentLevel = g_ContinueGame;
			m_seenRules = GR_HIGH_ENEMY_COUNT|GR_MED_ENEMY_COUNT|GR_LOW_ENEMY_COUNT;
			for ( int i = 0; i<m_currentLevel; ++i )
				m_seenRules |= level_rules[i];
			newScreen = new CutScene(m_currentLevel, m_seenRules);
			m_seenRules |= level_rules[m_currentLevel];
		
	}
	
	if ( newScreen )
		m_activeScreen = newScreen;
	else
	{
		exit(0);
	}
}

void GameManager::tick()
{
	if ( !m_activeScreen )
		return;

	m_activeScreen->tick();
	
	if ( m_activeScreen->finished() )
	{
		m_nextScreen = m_activeScreen->getNextScreen();
		delete m_activeScreen;
		m_activeScreen = NULL;
		
		newScreen( m_nextScreen );
		glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
	}
}

void GameManager::onEvent( SDL_Event *event )
{
	if ( !m_activeScreen )
		return;
	
	m_activeScreen->onEvent( event );
}

int main( int argc, char *argv[] )
{
// TODO: huh... I'd swear there was a bitflag for opengl specific double
// buffering, but it is not showing up in the wiki...
	int videoFlags = SDL_OPENGL|SDL_DOUBLEBUF;
	bool done = false, showfps = false;
	SDL_Event event;

	srand ( (unsigned int)time(NULL) );

	if ( SDL_Init( SDL_INIT_VIDEO | SDL_INIT_AUDIO ) < 0 )
	{
		fprintf( stderr, SDL_GetError() );
		exit(-1);
	}

	atexit(SDL_Quit);

// HACKETY-HACK: jeez, what a lame way to check if the game is being run
// from its own folder... is stat() even ISO compilant? i bet this aint gonna
// compile on anything that isnt VS
#ifdef _WIN32
	struct stat fi;
	if ( stat("sir.exe", &fi) )
	{
		exit(-1);
	}
#endif

// TODO: launch flag to enable debug mode? enable it from a code define?
	for(int i = 1; i < argc; ++i)
	{
		if(strncmp(argv[i], "-f", 2) == 0)
			videoFlags |= SDL_FULLSCREEN;
		else if ( strcmp(argv[i], "-showfps") == 0 )
			showfps = true;
	}

	g_Screen = SDL_SetVideoMode( SCREEN_W, SCREEN_H, 0, videoFlags );

	if ( !g_Screen )
	{
		fprintf( stderr, SDL_GetError() );
		exit(-1);
	}


	glInit();
	glResizeWindow( SCREEN_W, SCREEN_H );

	SDL_WM_SetCaption("Space Invaders: Rehash", "Space Invaders: Rehash");

	if(videoFlags & SDL_FULLSCREEN)
		SDL_ShowCursor(0);
		
	if(Mix_OpenAudio(22050, AUDIO_S16, 2, 4096))
	{
		printf("Unable to open audio!\n");
		exit(-1);
	}
	
	loadSounds();

	g_Fonts = new FontHandler();
	genEratosthenes();

	GameManager *gameManager = new GameManager();

	bool neednew = false;
	int T0     = 0;
	int Frames = 0;
	SimpleTimer t_cap;
	char fpst[8] = "LOADING";

	for ( int i=0; i<KEY_MAX; ++i )
	g_Keys[i] = false;

	while( !done )
	{
		t_cap.start();

		if(SDL_PollEvent(&event) > 0)
		{
			gameManager->onEvent(&event);
			switch (event.type)
			{
			case SDL_KEYUP:
				switch (event.key.keysym.sym)
				{
				case SDLK_ESCAPE: g_Keys[KSCAPE] = false; g_Keys[KPAUSE] = false; break;
				case SDLK_LEFT: g_Keys[KLEFT] = false; break;
				case SDLK_RIGHT: g_Keys[KRIGHT] = false; break;
				case SDLK_RETURN:
				case SDLK_SPACE: g_Keys[KSHOOT] = false; break;
				case SDLK_1:  g_Keys[K1] = false; break;
				case SDLK_2:  g_Keys[K2] = false; break;
				case SDLK_3:  g_Keys[K3] = false; break;
				case SDLK_4:  g_Keys[K4] = false; break;
				case SDLK_5:  g_Keys[K5] = false; break;
				case SDLK_6:  g_Keys[K6] = false; break;
				case SDLK_7:  g_Keys[K7] = false; break;
				case SDLK_8:  g_Keys[K8] = false; break;
				case SDLK_9:  g_Keys[K9] = false; break;
				case SDLK_q:  g_Keys[KQ] = false; break;
				case SDLK_w:  g_Keys[KW] = false; break;
				case SDLK_e:  g_Keys[KE] = false; break;
				case SDLK_r:  g_Keys[KR] = false; break;
				case SDLK_p:  g_Keys[KPAUSE] = false; break;
#ifdef USE_SKIP_CHEAT
				case SDLK_m:  g_Keys[KCHEAT] = false; break;
#endif
				case SDLK_UP:  g_Keys[KUP] = false; break;
				case SDLK_DOWN:  g_Keys[KDOWN] = false; break;
				}
				break;

			case SDL_QUIT:
				exit(0);

			case SDL_KEYDOWN:
				switch (event.key.keysym.sym)
				{
				case SDLK_ESCAPE: g_Keys[KSCAPE] = true; g_Keys[KPAUSE] = true; break;
				case SDLK_LEFT: g_Keys[KLEFT] = true; break;
				case SDLK_RIGHT: g_Keys[KRIGHT] = true; break;
				case SDLK_RETURN:
				case SDLK_SPACE: g_Keys[KSHOOT] = true; break;
				case SDLK_1:  g_Keys[K1] = true; break;
				case SDLK_2:  g_Keys[K2] = true; break;
				case SDLK_3:  g_Keys[K3] = true; break;
				case SDLK_4:  g_Keys[K4] = true; break;
				case SDLK_5:  g_Keys[K5] = true; break;
				case SDLK_6:  g_Keys[K6] = true; break;
				case SDLK_7:  g_Keys[K7] = true; break;
				case SDLK_8:  g_Keys[K8] = true; break;
				case SDLK_9:  g_Keys[K9] = true; break;
				case SDLK_q:  g_Keys[KQ] = true; break;
				case SDLK_w:  g_Keys[KW] = true; break;
				case SDLK_e:  g_Keys[KE] = true; break;
				case SDLK_r:  g_Keys[KR] = true; break;
#ifdef USE_SKIP_CHEAT
				case SDLK_m:  g_Keys[KCHEAT] = true; break;
#endif
				case SDLK_p:  g_Keys[KPAUSE] = true; break;
				case SDLK_UP:  g_Keys[KUP] = true; break;
				case SDLK_DOWN:  g_Keys[KDOWN] = true; break;
				}
				break;
			}
		}

		glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

		gameManager->tick();

		if ( showfps )
		{
			glEnable2D();
			glPrint(FONT_FPS, SCREEN_W - 50, SCREEN_H - 22, fpst); // 32 - 22
//			glPrintf(FONT_FPS, SCREEN_W - 120, SCREEN_H - 22,
//			"LEFT %d | RIGHT %d | SPACE %d",
//			g_Keys[KLEFT], g_Keys[KRIGHT], g_Keys[KSHOOT]);
			glDisable2D();
		}

		SDL_GL_SwapBuffers();

// HACKETY-HACK: Note to self, buy iron mask, Satan is going to burn my face off
// when he notices that im using fps capping... oh dear, i'm going to programmers'
// hell and i'm gonna spend all eternity programming in LISP.
#ifdef CAP_FPS
		if( t_cap.get_ticks() < 1000 / FRAMES_PER_SECOND )
		{
			SDL_Delay( (1000 / FRAMES_PER_SECOND) - t_cap.get_ticks() );
		}
#endif

// FIXME: fps counter not accurate enough?
		Frames++;
		int t = SDL_GetTicks();
		if (t - T0 >= 2000)
		{
			GLfloat seconds = (t - T0) / 1000.0f;
			GLfloat fps = Frames / seconds;
			sprintf(fpst, "%.1fFPS", fps);
			printf("%d frames in %g seconds = %g FPS\n", Frames, seconds, fps);
			T0 = t;
			Frames = 0;
		}
	}

	exit( 0 );
}
